<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // export命令
        // 模块功能主要有两个命令构成：export和import。
        // export命令用于规定模块的对外接口，import命令用于输入其他模块提供的功能


        // 一个模块就是一个独立的文件。 该文件内部的所有变量， 外部无法获取。 
        // 如果你希望外部能够读取模块内部的某个变量， 就必须使用export关键字输出该变量。 
        // 下面是一个 JS 文件， 里面使用export命令输出变量。

        // profile.js
        // export var firstName = 'Michel';
        // export var lastName = 'Jackson';
        // export var year = 2001;
        // 上面代码是profile.js文件，保存了用户信息。
        // ES6 将其视为一个模块，里面用export命令对外部输出了三个变量。

        // export的写法，除了像上面这样，还有另外一种。
        // prort.js
        // var firstName = 'Michael';
        // var lastName = 'Jackson';
        // var year = 1958;
        // export {
        //     firstName,
        //     lastName,
        //     year
        // };
        // 上面代码在export命令后面，使用大括号指定所要输出的一组变量。
        // 它与前一种写法（直接放置在var语句前）是等价的，但是应该优先考虑使用这种写法。
        // 因为这样就可以在脚本尾部，一眼看清楚输出了哪些变量。


        // export命令除了输出变量，还可以输出函数或类（class）
        // export function multiply(x, y) {
        //     return x * y;
        // }
        // 上面代码对外输出一个函数multiply


        // 通常情况下， export输出的变量就是本来的名字， 但是可以使用as关键字重命名。
        // function v1() {}

        // function v2() {}

        // export {
        //     v1 as streamV1,
        //     v2 as streamV2,
        //     v2 as streamLatestVersion
        // };
        // 上面代码使用as关键字，重命名了函数v1和v2的对外接口。
        // 重命名后，v2可以用不同的名字输出两次。

        // 需要特别注意的是，export命令规定的是对外的接口
        // 必须与模块内部的变量建立一一对应关系

        // 报错
        // export 1;
        // 报错
        // var m = 1;
        // export m;
        // 上面两种写法都会报错，因为没有提供对外的接口。
        // 第一种写法直接输出 1，第二种写法通过变量m，还是直接输出 1。
        // 1只是一个值，不是接口。正确的写法是下面这样。
        // // 写法一
        // export var m = 1;
        // // 写法二
        // var m = 1;
        // export {
        //     m
        // };
        // // 写法三
        // var n = 1;
        // export {
        //     n as m
        // };
        // 上面三种写法都是正确的，规定了对外的接口m。
        // 其他脚本可以通过这个接口，取到值1。
        // 它们的实质是，在接口名与模块内部变量之间，建立了一一对应的关系。



        // 同样的，function和class的输出，也必须遵守这样的写法。
        // 报错
        // function f() {}
        // export f;
        // // 正确
        // export function f() {};
        // // 正确
        // function f() {}
        // export {
        //     f
        // };


        // 另外，export语句输出的接口，与其对应的值是动态绑定关系，
        // 即通过该接口，可以取到模块内部实时的值。
        // export var foo = 'bar';
        // setTimeout(() => foo = 'baz', 2000);
        // 输出变量foo,值为bar,500毫秒后变成baz



        // 最后，export命令可以出现在模块的任何位置，只要处于模块顶层就可以。
        // 如果处于块级作用域内，就会报错，下一节的import命令也是如此。
        // 这是因为处于条件代码块之中，就没法做静态优化了，违背了 ES6 模块的设计初衷。
        // function foo() {
        //     export default 'bar' // SyntaxError
        // }
        // foo()
        // 上面代码中，export语句放在函数之中，结果报错。



        // improt命令
        // 使用exprot命令定义了模块的对外接口以后,其他JS文件就可以通过
        // improt命令加载这个模块
        // main.js
        // import {
        //     firstName,
        //     lastName,
        //     year
        // } from './profile.js';

        // function setName(element) {
        //     element.textContent = firstName + ' ' + lastName;
        // }
        // 上面代码的import命令，用于加载profile.js文件，并从中输入变量。import命令接受一对大括号，里面指定要从其他模块导入的变量名。
        // 大括号里面的变量名，必须与被导入模块（profile.js）对外接口的名称相同。

        // 如果想为输入的变量重新取一个名字，import命令要使用as关键字，将输入的变量重命名
        // import {
        //     lastName as surname
        // } from './profile.js';


        // import命令输入的变量都是只读的，因为它的本质是输入接口。
        // 也就是说，不允许在加载模块的脚本里面，改写接口。
        // import {
        //     a
        // } from './xxx.js'

        // a = {}; // Syntax Error : 'a' is read-only;
        // 上面代码中，脚本加载了变量a，对其重新赋值就会报错，因为a是一个只读的接口。
        // 但是，如果a是一个对象，改写a的属性是允许的。

        // import {
        //     a
        // } from './xxx.js'

        // a.foo = 'hello'; // 合法操作
        // 上面代码中，a的属性可以成功改写，并且其他模块也可以读到改写后的值。
        // 不过，这种写法很难查错，建议凡是输入的变量，都当作完全只读，不要轻易改变它的属性。


        // import后面的from指定模块文件的位置， 可以是相对路径， 也可以是绝对路径，.js后缀可以省略。 
        // 如果只是模块名， 不带有路径， 那么必须有配置文件， 告诉 JavaScript 引擎该模块的位置
        // import {
        //     myMethod
        // } from 'util';
        // 上面代码中，util是模块文件名，由于不带有路径，
        // 必须通过配置，告诉引擎怎么取到这个模块。


        // 注意,import命令具有提升效果,会提升到整个模块的头部,首先执行
        // foo();
        // import {
        //     foo
        // } from 'my_module';
        // 上面的代码不会报错，因为import的执行早于foo的调用。
        // 这种行为的本质是，import命令是编译阶段执行的，在代码运行之前。


        // 由于import是静态执行，所以不能使用表达式和变量，
        // 这些只有在运行时才能得到结果的语法结构。
        // // 报错
        // import {
        //     'f' + 'oo'
        // } from 'my_module';
        // // 报错
        // let module = 'my_module';
        // import {
        //     foo
        // } from module;
        // // 报错
        // if (x === 1) {
        //     import {
        //         foo
        //     } from 'module1';
        // } else {
        //     import {
        //         foo
        //     } from 'module2';
        // }
        // 上面三种写法都会报错，因为它们用到了表达式、变量和if结构。在静态分析阶段，这些语法都是没法得到值的。


        // 最后,import语句会执行所加载的模块,因此可以有下面的写法
        // import 'lodash';
        // 上面代码仅仅执行lodash模块,但是不输入任何值


        // 如果多次重复执行同一句import语句，那么只会执行一次，而不会执行多次
        // import 'lodash';
        // import 'lodash';
        // 上面代码加载了两次,但是只会执行一次

        // import {
        //     foo
        // } from 'my_modul';
        // import {
        //     bar
        // } from 'my_modul';
        // // 等同于
        // import {
        //     foo,
        //     bar
        // } from 'my_modul';
        // 上面代码中，虽然foo和bar在两个语句中加载，但是它们对应的是同一个my_module实例。
        // 也就是说，import语句是 Singleton 模式。



        // 模块的整体加载
        // 除了指定加载某个输出值，还可以使用整体加载，
        // 即用星号（）指定一个对象，所有输出值都加载在这个对象上面。
        // circle.js
        // export function area(radius) {
        //     return Math.PI * radius * radius;
        // }
        // export function circumference(radius) {
        //     return 2 * Math.PI * radius;
        // }

        // 现在，加载这个模块
        // main.js
        // import {
        //     area,
        //     circumference
        // } from './circle';
        // console.log('圆面积：' + area(4));
        // console.log('圆周长：' + circumference(14));
        // 上面写法是逐一指定要加载的方法，整体加载的写法如下。
        // import * as circle from './circle';
        // console.log('圆面积：' + circle.area(4));
        // console.log('圆周长：' + circle.circumference(14));


        // 注意，模块整体加载所在的那个对象（上例是circle），应该是可以静态分析的，
        // 所以不允许运行时改变。下面的写法都是不允许的。
        // import * as circle from './circle';
        // // 下面两行都是不允许的
        // circle.foo = 'hello';
        // circle.area = function() {};


        // export defult 命令
        // exprot-default.js
        // export default function() {
        //     console.log('foo');
        // }
        // 上面代码是一个模块文件export-default.js，它的默认输出是一个函数。


        // 其他模块加载该模块时，import命令可以为该匿名函数指定任意名字。
        // import customName from './export-default';
        // customName(); //'foo'
        // 上面代码的import命令，可以用任意名称指向export-default.js输出的方法，这时就不需要知道原模块输出的函数名。
        // 需要注意的是，这时import命令后面，不使用大括号。



        // export default命令用在非匿名函数前，也是可以的。
        // export-default.js
        // export default function foo() {
        //     console.log('foo');
        // }
        // 或者写成
        // function foo() {
        //     console.log('foo');
        // }
        // export default foo;
        // 上面代码中，foo函数的函数名foo，在模块外部是无效的。
        // 加载的时候，视同匿名函数加载。


        // 下面比较一下默认输出和正常输出。
        // 第一组
        // export default function crc32() {
        //     // 输出
        // }
        // import crc32 from 'crc32'; //输入
        // 第二组
        // export function crc32() {
        //     // 输出
        // }
        // import {
        //     crc32
        // } from 'crc32'; //输入
        // 上面代码的两组写法，第一组是使用export default时，对应的import语句不需要使用大括号；
        // 第二组是不使用export default时，对应的import语句需要使用大括号。



        // export default命令用于指定模块的默认输出。
        // 显然，一个模块只能有一个默认输出，因此export default命令只能使用一次。
        // 所以，import命令后面才不用加大括号，因为只可能唯一对应export default命令。

        // 加载规则
        // 浏览器加载 ES6 模块，也使用<script>标签，但是要加入type="module"属性。
    </script>
    <script type="module" src="../foo.js">
        // 该标签在网页中插入一个模块foo.js， 由于type属性设为module，所以浏览器知道这是一个 ES6 模块。
    </script>
</body>

</html>